typedef struct {
  uint8_t button_pressed;
} __attribute__((packed)) outgoing_msg;

typedef struct {
  uint8_t led_on;
} __attribute__((packed)) incoming_msg;

uint8_t crc8(const void* vptr, int len) {
  const uint8_t *data = (const uint8_t *) vptr;
  unsigned crc = 0;
  int i, j;
  for (j = len; j; j--, data++) {
    crc ^= (*data << 8);
    for(i = 8; i; i--) {
      if (crc & 0x8000)
        crc ^= (0x1070 << 3);
      crc <<= 1;
    }
  }
  return (uint8_t)(crc >> 8);
}

#define MSG_HEADER ((char) 0xaa)
incoming_msg msg;
int msg_len = -1;
bool received_byte(char b) {
  char *msg_ptr = (char*) &msg;
  if (msg_len == -1) {
    if (b == MSG_HEADER) {
      // received frame header
      msg_len = 0;
      return false;
    } else {
      // frame header not received, skipping
      return false;
    }
  } else if (msg_len < sizeof(incoming_msg)) {
    // receiving payload
    msg_ptr[msg_len++] = b;
    return false;
  } else {
    // received crc8 checksum
    uint8_t actual_crc8 = crc8(msg_ptr, sizeof(incoming_msg));
    if (actual_crc8 == b) {
      // checksum matches
      // prepare for next frame
      msg_len = -1;
      return true;
    } else {
      // checksum mismatch
      for (size_t i = 0; i < sizeof(incoming_msg); i++) {
        if (msg_ptr[i] == MSG_HEADER) {
          msg_len = sizeof(incoming_msg) - (i + 1);
          memmove(msg_ptr, msg_ptr + (i + 1), msg_len);
          msg_ptr[msg_len++] = b;
          return false;
        }
      }
      if (b == MSG_HEADER) {
        msg_len = 0;
        return false;
      } else {
        msg_len = -1;
        return false;
      }
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(2, OUTPUT);
  digitalWrite(2, HIGH);
  pinMode(3, INPUT_PULLUP);
}

void loop() {
  while (Serial.available() > 0) {
    if (received_byte(Serial.read())) {
      if (msg.led_on) {
        digitalWrite(2, LOW);
      } else {
        digitalWrite(2, HIGH);
      }
    }
  }

  Serial.write(0xaa);
  outgoing_msg msg_out;
  if (digitalRead(3) == HIGH) {
    msg_out.button_pressed = 0;
  } else {
    msg_out.button_pressed = 1;
  }
  Serial.write((char*) &msg_out, sizeof(outgoing_msg));
  Serial.write(crc8(&msg_out, sizeof(outgoing_msg)));
  delay(50);
}
